import ctypes
from numpy import random
import pandas as pd
class RequestGenerator:
    def __init__(self, cloud_system, args):
        self.cloud_system=cloud_system
        #加载trace数据
        self.file_pd=pd.read_csv(args.application_trace_file, dtype={'rpc_id': 'str'})
        #获取deadline信息
        self.init_deadline_info()
        #记录到来的用户请求的数量
        self.request_come_num=0
        self.this_rows_order=0
        self.next_interval=0
        self.all_rows=self.file_pd.shape[0]
        return
    
    def generate_request(self):
        this_trace_id=self.file_pd.iloc[self.this_rows_order]["traceid"]
        end_rows_order=self.this_rows_order
        #找到连续的traceid对应的数据
        while True:
            if end_rows_order+1<=self.all_rows-1 and this_trace_id==self.file_pd.iloc[end_rows_order+1]["traceid"]:
                end_rows_order+=1
            else:
                break
        trace_data=self.file_pd.iloc[self.this_rows_order:end_rows_order+1]
        deadline=self.deadline_info[trace_data.iloc[0]["service"]]
        request_new=Request(self.cloud_system, self.cloud_system.env, self.request_come_num, trace_data,deadline,self.cloud_system.print_level)
        
        #更新下一个请求到来间隔
        if end_rows_order+1<=self.all_rows-1:
            self.next_interval=self.file_pd.iloc[end_rows_order+1]["timestamp"]-self.file_pd.iloc[self.this_rows_order]["timestamp"]
        else:
            self.next_interval=-1
        #更新索引    
        self.this_rows_order=end_rows_order+1
        #更新到来的请求数量
        if request_new.generate_flage==True:
            self.request_come_num+=1
        
        return request_new if request_new.generate_flage else None
    
    def init_deadline_info(self):
        self.deadline_info={}
        for index,row in self.file_pd.iterrows():
            if row["um"]=="USER" and row["rpctype"]=="http" and str(row["rpc_id"])=='0':
                service_name=row["service"]
                rt=row["rt"]
                if service_name not in self.deadline_info.keys():
                    self.deadline_info[service_name]=rt
                else:
                    self.deadline_info[service_name]=max(self.deadline_info[service_name],rt)
        return 
    
    def get_next_interval(self):
        return self.next_interval
        
    def is_end(self):
        return self.this_rows_order>self.all_rows-1


class Request:
    def __init__(self, cloud_system, env, request_id, trace_data, time_deadline,print_level):
        
        self.cloud_system=cloud_system
        #仿真环境
        self.env=env
        self.print_level=print_level
        #请求ID
        self.request_id=request_id
        #到达时间
        self.arrive_time=env.now
        self.complete_time=-1
        #原始数据，用于生成call graph
        self.trace_data=trace_data
        #所有任务数量
        self.all_tasks_num = 0
        
        self.trace_id=self.trace_data.iloc[0]["traceid"]
        self.service_name=self.trace_data.iloc[0]["service"]
            
        #完成任务数量
        self.finished_task_num = 0
        #完成标志
        self.back_task_num=0
        self.need_back_task_num=0
        self.back_flage=False
        self.within_deadline_flage=True

        #截止时间,一段时间，非时间点
        self.time_deadline=time_deadline
        
        #是否生成成功过的标志
        self.generate_flage=True
        self.succeed_flage=True
        self.fault_flage=False
        
        self.service_name=None
        #生成任务序列
        self.generate_task_sequnce_enter()
         



    #生成任务序列
    def generate_task_sequnce_enter(self):
        if self.print_level>10:
            print(f"this request trace data:\n{self.trace_data}")
        df_temp=self.trace_data[(self.trace_data["um"] == "USER") & (self.trace_data["rpc_id"].isin(['0']) & (self.trace_data["rpctype"]=="http"))]
        # 获取要剔除的行的索引  
        indices_to_remove = df_temp.index  
        # 更新 trace_data，剔除 df_temp 的数据  
        self.trace_data = self.trace_data.drop(indices_to_remove) 
         
        if len(df_temp) ==0:
            print("request init wrong! (No enter trace info)")
            self.generate_flage=False
            return 
        #用于给task编号
        self.task_id_order = 0
        self.start_task_list=[]
        for index, row in df_temp.iterrows():
            rpc_id=row["rpc_id"]
            rpc_type=row["rpctype"]
            ms_kind=row["dm"] #哪种类型的微服务可以完成这个task
            rt=row["rt"]
            start_time_in_trace_data=row["timestamp"]
            
            # if rpc_id!="0":
            #     self.generate_flage=False
            #     return None
            if rpc_type!="mq":
                self.need_back_task_num+=1
                
            #生成task名字，其中包含request名字
            task_id=f"R_{self.request_id}-T_{self.task_id_order}"
            self.task_id_order+=1
            start_task = Task(self.env, self, task_id, None, ms_kind, rpc_id, rpc_type, rt,start_time_in_trace_data,self.print_level)
            new_task_next_level_tasks_list = self.generate_next_level_tasks(start_task)
            start_task.set_next_tasks(new_task_next_level_tasks_list)
            self.start_task_list.append(start_task)
        
            
        return
    #生成下一级任务，并返回list
    def generate_next_level_tasks(self,parent_task):
        next_level_task_index_list=self.get_next_level_task_index(parent_task)
        
        next_level_tasks_list=[]
        for index in next_level_task_index_list:
            rpc_id=self.trace_data.loc[index, "rpc_id"]
            self.trace_data.loc[index, "rpc_id"]="None"
            rpc_type=self.trace_data.loc[index,"rpctype"]
            ms_kind=self.trace_data.loc[index,"dm"] #哪种类型的微服务可以完成这个task
            rt=self.trace_data.loc[index,"rt"]
            start_time_in_trace_data=self.trace_data.loc[index,"timestamp"]
            
            #生成task名字，其中包含request名字
            task_id=f"R_{self.request_id}-T_{self.task_id_order}"
            self.task_id_order+=1
            new_task = Task(self.env, self, task_id, parent_task, ms_kind, rpc_id, rpc_type, rt,start_time_in_trace_data,self.print_level)
            
            new_task_next_level_tasks_list=self.generate_next_level_tasks(new_task)
            new_task.set_next_tasks(new_task_next_level_tasks_list)
            new_task.start_after_parent_time=round(new_task.start_time_in_trace_data-parent_task.start_time_in_trace_data)
            new_task.end_before_parent_time=round(parent_task.start_time_in_trace_data+parent_task.rt-new_task.start_time_in_trace_data-new_task.rt)
            if rpc_type!= "mq" and (new_task.start_after_parent_time<0 or new_task.end_before_parent_time<0):
                print("generate_next_level_tasks wrong!")
                exit(-1)
            next_level_tasks_list.append(new_task)
        return next_level_tasks_list

    #从trace_data中获取下一级task的index
    def get_next_level_task_index(self,this_task):
        index_list=[]
        for index, row in self.trace_data.iterrows():
            #满足三个条件则认为当前行对应子任务：rpc_id 包含关系，当前rpc_id是目标rpc_id最大的被包含的值，微服务类型一致
            if row["rpc_id"].startswith(this_task.rpc_id) and self.is_max_startswith(row["rpc_id"],this_task.rpc_id, row["um"]) and row["um"]==this_task.ms_kind:    
                index_list.append(index)
        
        return index_list

    def is_max_startswith(self, next_level_rpcid, this_level_rpcid, um_kind):
        this_length=0
        if next_level_rpcid.startswith(this_level_rpcid):
            this_length=len(this_level_rpcid)
        if len(next_level_rpcid)==len(this_level_rpcid):
            return False
        for index, row in self.trace_data.iterrows():
            if next_level_rpcid.startswith(row["rpc_id"]) and len(next_level_rpcid)>len(row["rpc_id"]) and len(row["rpc_id"])>this_length and um_kind==row["um"]:
                return False
        return True
                
    def back_one_task(self,fault_flage):
        if fault_flage:
            self.fault_flage=fault_flage
        self.back_task_num+=1
        if self.back_task_num==self.need_back_task_num:
            self.complete_time=self.env.now
            if self.complete_time-self.arrive_time>self.time_deadline or self.fault_flage==True:
                self.succeed_flage=False
            self.cloud_system.end_one_request(self)
        return
    
    
    

    


class Task:
    def __init__(self, env, request, task_id, parent_task, ms_kind, rpc_id, rpc_type, rt, start_time_in_trace_data,print_level):
        self.env=env
        self.print_level=print_level
        #请求对象
        self.request=request  # need test
        #任务ID
        self.task_id=task_id
        #上一级任务
        self.parent_task=parent_task
        #任务对应的微服务类型
        self.ms_kind=ms_kind
        #请求类型，决定是否等待返回值
        self.rpc_type=rpc_type
        self.rpc_id=rpc_id
        #响应时间，也叫处理时间
        self.rt=rt
        self.start_time_in_trace_data=start_time_in_trace_data
        #下一级任务list
        self.next_tasks_list=[]

        self.fault_flage = False
        self.back_flage=False
        self.back_task_num=0
        self.need_back_task_num=0
        self.start_after_parent_time=0
        self.end_before_parent_time=0
        # 是否可以返回
        self.all_sub_back_event = self.request.env.event()
        if self.print_level>10:
            parent_task_id=parent_task.task_id if parent_task!=None else "None"
            print(f"task {self.task_id} init, with ms_kind:{self.ms_kind}\tparent_task:{parent_task_id}, rpc_type:{rpc_type}...")
        return
    #  list with (invoction_time, sub_task)
    def set_next_tasks(self,next_tasks_list):
        self.next_tasks_list=next_tasks_list
        for task in self.next_tasks_list:
            if task.rpc_type!="mq":
                self.need_back_task_num+=1
        return


    def task_with_error(self):
        self.fault_flage=True
        
    def back_one_task(self, fault_flage, end_delay_time):
        if fault_flage:
            self.fault_flage=True
        self.env.process(self.back_one_task_delay(end_delay_time))
        
    def back_one_task_delay(self, end_delay_time):
        yield self.env.timeout(end_delay_time)
        self.back_task_num+=1
        if self.back_task_num==self.need_back_task_num:
            self.all_sub_back_event.succeed()

    def back_to_request(self):
        # self.request.fault_flage=self.fault_flage
        self.request.back_one_task(self.fault_flage)
        
        
#用于记录执行过程
class TaskDealRecord:
    def __init__(self,request_addr,task_addr,parent_task_record_addr,parent_task_node_id):
        self.request=ctypes.cast(request_addr,ctypes.py_object).value
        self.task=ctypes.cast(task_addr,ctypes.py_object).value

        if parent_task_record_addr == None:
            self.parent_task_record= None
        else:
            self.parent_task_record = ctypes.cast(parent_task_record_addr,ctypes.py_object).value

        #父task record 执行地点，用于返回请求结果
        self.parent_task_node_id=parent_task_node_id

        # 本task record ID
        self.task_record_id = self.task.task_id + "_" + random.randint(100000, 1000000).__str__()

        #记录单次执行是否出错
        self._fault_flage = False
        #本实例的处理次数
        self._deal_num=0
        # 失败次数
        self._fault_num = 0

        # 任务进入队列的时间
        self._arrive_time = None
        # 失败原因
        self._reason_for_failure = None

        # 完成标志
        self._finish_flage = False

        # 是否返回上级的标致，为True表示已经返回了
        self._back_flage = False
        # 是否可以返回
        self.back_event = self.request.env.event()
        # 记录需要返回的下一级task
        self._back_task = {}
        self._generate_back_task()

    #记录到达时间
    def recored_arrive_time(self,time_f):
        self._arrive_time=time_f

    # 判断是否继续提交或者重试
    def can_retry_or_resubmit(self):
        if (self.task.fault_tolerant_strategy == "retry" or self.task.fault_tolerant_strategy == "resubmit") and self._deal_num < self.task.strategy_times:
            return True
        else:
            return False

    def reset_for_retry_or_resubmit(self):
        # 把错误记录器重置
        self._fault_flage = False
        return


    def task_with_error(self,reason_f):
        self._fault_flage = True
        self._reason_for_failure=reason_f

    def get_reason_for_failure(self):
        return self._reason_for_failure

    # 任务成功
    def judge_task_succeed(self):
        is_succeed=True

        #本实体处理次数加一，任务执行次数加一
        self._deal_num += 1
        if self._fault_flage == True:
            #任务错误次数加一
            self._fault_num+=1
            is_succeed=False
        else:
            self._finish_flage = True

        return is_succeed

    def generate_next_level_task_record_list(self,parent_task_node_id):
        next_level_task_record_list=[]
        for next_level_task in self.task.next_tasks_list:
            if next_level_task.fault_tolerant_strategy == "replication":
                new_task_record_list=[TaskDealRecord(id(self.request),id(next_level_task),id(self),parent_task_node_id) for _ in range(next_level_task.strategy_times)]
                next_level_task_record_list.extend(new_task_record_list)
            else:
                new_task_record=TaskDealRecord(id(self.request),id(next_level_task),id(self),parent_task_node_id)
                next_level_task_record_list.append(new_task_record)

        return next_level_task_record_list

    def _generate_back_task(self):
        for next_task_f in self.task.next_tasks_list:
            if next_task_f.call_type != "mq":
                self._back_task[next_task_f.task_id]=False
        if self._back_task == {}:
            self.back_event.succeed(value="back true")
        return



    def back_to_parent(self):
        if self.parent_task_record == None:
            self.request.request_back()
        else:
            self.parent_task_record.set_back_task(self.task.task_id)

    def _is_all_back(self):
        for task_id in self._back_task.keys():
            if self._back_task[task_id] == False:
                return False
        return True

    def set_back_task(self,back_task_id):
        self._back_task[back_task_id]=True
        if self._is_all_back() and self._back_flage==False:
            self._back_flage=True
            self.back_event.succeed(value="back true")
        return
    def __del__(self):
        print("task is deleted")

    def deep_copy(self):
        return TaskDealRecord(id(self.request),id(self.task),id(self.parent_task_record),self.parent_task_node_id)
    def judge_out_deadline(self,std_time):
        if self.request.arrive_time + self.request.time_deadline < std_time:
            return True
        else:
            return False


# import queue
# class StatisticQueue:
#     def __init__(self,max_num):
#         self.max_num=max_num
#         self.queue=queue.Queue(max_num)
#         self.success_num=0
#     def add_success(self):
#         if self.queue.qsize()==self.max_num:
#             temp=self.queue.get()
#             if temp == 1:
#                 self.success_num-=1
#
#         self.queue.put(1)
#         self.success_num += 1
#     def add_failure(self):
#
#         if self.queue.qsize()==self.max_num:
#             temp=self.queue.get()
#             if temp == 1:
#                 self.success_num-=1
#
#         self.queue.put(0)
#
#     def get_succeed_rate(self):
#         if self.queue.qsize()==0:
#             return 0
#         else:
#             return self.success_num/self.queue.qsize()



